﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.NetworkInformation;
using System.Net;
using System.Net.Sockets;

namespace NetTraceLib
{
    public class RouteNode
    {
        public bool Intermediate { get; set; }

        private IPAddress address;
        public IPAddress Address {
            get { return address; } 
            set 
             {
                 if (!IPAddress.Equals(value, address))
                 {
                     address = value;
                     HostEntry = null;
                     NodeName = GetDefaultNodeName();
                 }
            } 
        }

        public IPHostEntry HostEntry { get; protected set; }
        public int Distance { get; set; }
        public int Timeout { get; set; }

        public string NodeName { get; protected set; }

        public NodeStatus Status { get; protected set; }
        public Queue<NodeStatus> StatusHistory { get; protected set; }
        public IPAddress Responder { get; protected set; }

        public Exception LastException { get; protected set; }
        public bool Busy { get; protected set; }
        public bool Obsolete { get; set; }
        

        private Ping ping;
        private PingOptions pingOptions;
        private byte[] magic;
        private int maximumStatusHistory;

        public RouteNode( int distance, byte[] magic, int maximumStatusHistory, int timeout)
        {
            this.Distance = distance;
            this.NodeName = GetDefaultNodeName();
            this.magic = magic;
            this.maximumStatusHistory = maximumStatusHistory;
            this.Timeout = timeout;
            this.Status = new NodeStatus(null, 0, IPStatus.Unknown);
            this.StatusHistory = new Queue<NodeStatus>(maximumStatusHistory);

            pingOptions = new PingOptions(distance, true);
        }


        public async Task UpdateCycle()
        {
            IPAddress currentAddress;

            if (Address == null)
                return;

            // our address might change if the route is being re-traced.
            currentAddress = Address; 
            try
            {
                PingReply reply;
                
                if (ping == null)
                    ping = new Ping();

                Busy = true;
                reply = await ping.SendPingAsync(currentAddress, Timeout, magic, pingOptions);
                Busy = false;

                Responder = reply.Address;
                Status = new NodeStatus(currentAddress, reply.RoundtripTime, reply.Status);
                AddStatusToHistory();
            }
            catch (Exception e)
            {
                LastException = e;
                Status = new NodeStatus(currentAddress, Int32.MaxValue, IPStatus.Unknown);
                AddStatusToHistory();
            }
        }

        public async Task UpdateHostInformation()
        {
                HostEntry = null;
                try
                {
                    HostEntry = await Dns.GetHostEntryAsync(Address);
                    NodeName = HostEntry.HostName;
                }
                catch (SocketException e)
                {
                    HostEntry = null;
                    NodeName = GetDefaultNodeName();
                } 
        }

        private string GetDefaultNodeName()
        {
            if (address == null)
            {
                return String.Format("Hop {0}", Distance);
            }
            else
            {
                return address.ToString();
            }
        }

        private void AddStatusToHistory()
        {
            StatusHistory.Enqueue(Status);
            if(StatusHistory.Count > maximumStatusHistory )
            {
                StatusHistory.Dequeue();
            }
        }
    }
}
